/*
 * Copyright 2010 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.android;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Date;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import com.datatypes.Track;
import com.facebook.android.AsyncFacebookRunner.RequestListener;

/**
 * Utility class supporting the Facebook Object.
 * 
 * @author ssoneff@facebook.com
 * 
 */
public final class FacebookUtils {

	/**
	 * Generate the multi-part post body providing the parameters and boundary
	 * string
	 * 
	 * @param parameters
	 *            the parameters need to be posted
	 * @param boundary
	 *            the random string as boundary
	 * @return a string of the post body
	 */
	public static String encodePostBody(Bundle parameters, String boundary) {
		if (parameters == null)
			return "";
		StringBuilder sb = new StringBuilder();

		for (String key : parameters.keySet()) {
			if (parameters.getByteArray(key) != null) {
				continue;
			}

			sb.append("Content-Disposition: form-data; name=\"" + key
					+ "\"\r\n\r\n" + parameters.getString(key));
			sb.append("\r\n" + "--" + boundary + "\r\n");
		}

		return sb.toString();
	}

	public static String encodeUrl(Bundle parameters) {
		if (parameters == null) {
			return "";
		}

		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (String key : parameters.keySet()) {
			if (first)
				first = false;
			else
				sb.append("&");
			sb.append(URLEncoder.encode(key) + "="
					+ URLEncoder.encode(parameters.getString(key)));
		}
		return sb.toString();
	}

	public static Bundle decodeUrl(String s) {
		Bundle params = new Bundle();
		if (s != null) {
			String array[] = s.split("&");
			for (String parameter : array) {
				String v[] = parameter.split("=");
				params.putString(URLDecoder.decode(v[0]),
						URLDecoder.decode(v[1]));
			}
		}
		return params;
	}

	/**
	 * Parse a URL query and fragment parameters into a key-value bundle.
	 * 
	 * @param url
	 *            the URL to parse
	 * @return a dictionary bundle of keys and values
	 */
	public static Bundle parseUrl(String url) {
		// hack to prevent MalformedURLException
		url = url.replace("fbconnect", "http");
		try {
			URL u = new URL(url);
			Bundle b = decodeUrl(u.getQuery());
			b.putAll(decodeUrl(u.getRef()));
			return b;
		} catch (MalformedURLException e) {
			return new Bundle();
		}
	}

	/**
	 * Connect to an HTTP URL and return the response as a string.
	 * 
	 * Note that the HTTP method override is used on non-GET requests. (i.e.
	 * requests are made as "POST" with method specified in the body).
	 * 
	 * @param url
	 *            - the resource to open: must be a welformed URL
	 * @param method
	 *            - the HTTP method to use ("GET", "POST", etc.)
	 * @param params
	 *            - the query parameter for the URL (e.g. access_token=foo)
	 * @return the URL contents as a String
	 * @throws MalformedURLException
	 *             - if the URL format is invalid
	 * @throws IOException
	 *             - if a network problem occurs
	 */
	public static String openUrl(String url, String method, Bundle params)
			throws MalformedURLException, IOException {
		// random string as boundary for multi-part http post
		String strBoundary = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
		String endLine = "\r\n";

		OutputStream os;

		if (method.equals("GET")) {
			url = url + "?" + encodeUrl(params);
		}
		Log.d("Facebook-Util", method + " URL: " + url);
		HttpURLConnection conn = (HttpURLConnection) new URL(url)
				.openConnection();
		conn.setRequestProperty("User-Agent", System.getProperties()
				.getProperty("http.agent") + " FacebookAndroidSDK");
		if (!method.equals("GET")) {
			Bundle dataparams = new Bundle();
			for (String key : params.keySet()) {
				if (params.getByteArray(key) != null) {
					dataparams.putByteArray(key, params.getByteArray(key));
				}
			}

			// use method override
			if (!params.containsKey("method")) {
				params.putString("method", method);
			}

			if (params.containsKey("access_token")) {
				String decoded_token = URLDecoder.decode(params
						.getString("access_token"));
				params.putString("access_token", decoded_token);
			}

			conn.setRequestMethod("POST");
			conn.setRequestProperty("Content-Type",
					"multipart/form-data;boundary=" + strBoundary);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setRequestProperty("Connection", "Keep-Alive");
			conn.connect();
			os = new BufferedOutputStream(conn.getOutputStream());

			os.write(("--" + strBoundary + endLine).getBytes());
			os.write((encodePostBody(params, strBoundary)).getBytes());
			os.write((endLine + "--" + strBoundary + endLine).getBytes());

			if (!dataparams.isEmpty()) {

				for (String key : dataparams.keySet()) {
					os.write(("Content-Disposition: form-data; filename=\""
							+ key + "\"" + endLine).getBytes());
					os.write(("Content-Type: content/unknown" + endLine + endLine)
							.getBytes());
					os.write(dataparams.getByteArray(key));
					os.write((endLine + "--" + strBoundary + endLine)
							.getBytes());

				}
			}
			os.flush();
		}

		String response = "";
		try {
			response = read(conn.getInputStream());
		} catch (FileNotFoundException e) {
			// Error Stream contains JSON that we can parse to a FB error
			response = read(conn.getErrorStream());
		}
		return response;
	}

	private static String read(InputStream in) throws IOException {
		StringBuilder sb = new StringBuilder();
		BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000);
		for (String line = r.readLine(); line != null; line = r.readLine()) {
			sb.append(line);
		}
		in.close();
		return sb.toString();
	}

	public static void clearCookies(Context context) {
		// Edge case: an illegal state exception is thrown if an instance of
		// CookieSyncManager has not be created. CookieSyncManager is normally
		// created by a WebKit view, but this might happen if you start the
		// app, restore saved state, and click logout before running a UI
		// dialog in a WebView -- in which case the app crashes
		@SuppressWarnings("unused")
		CookieSyncManager cookieSyncMngr = CookieSyncManager
				.createInstance(context);
		CookieManager cookieManager = CookieManager.getInstance();
		cookieManager.removeAllCookie();
	}

	/**
	 * Parse a server response into a JSON Object. This is a basic
	 * implementation using org.json.JSONObject representation. More
	 * sophisticated applications may wish to do their own parsing.
	 * 
	 * The parsed JSON is checked for a variety of error fields and a
	 * FacebookException is thrown if an error condition is set, populated with
	 * the error message and error type or code if available.
	 * 
	 * @param response
	 *            - string representation of the response
	 * @return the response as a JSON Object
	 * @throws JSONException
	 *             - if the response is not valid JSON
	 * @throws FacebookError
	 *             - if an error condition is set
	 */
	public static JSONObject parseJson(String response) throws JSONException,
			FacebookError {
		// Edge case: when sending a POST request to /[post_id]/likes
		// the return value is 'true' or 'false'. Unfortunately
		// these values cause the JSONObject constructor to throw
		// an exception.
		if (response.equals("false")) {
			throw new FacebookError("request failed");
		}
		if (response.equals("true")) {
			response = "{value : true}";
		}
		JSONObject json = new JSONObject(response);

		// errors set by the server are not consistent
		// they depend on the method and endpoint
		if (json.has("error")) {
			JSONObject error = json.getJSONObject("error");
			throw new FacebookError(error.getString("message"),
					error.getString("type"), 0);
		}
		if (json.has("error_code") && json.has("error_msg")) {
			throw new FacebookError(json.getString("error_msg"), "",
					Integer.parseInt(json.getString("error_code")));
		}
		if (json.has("error_code")) {
			throw new FacebookError("request failed", "", Integer.parseInt(json
					.getString("error_code")));
		}
		if (json.has("error_msg")) {
			throw new FacebookError(json.getString("error_msg"));
		}
		if (json.has("error_reason")) {
			throw new FacebookError(json.getString("error_reason"));
		}
		return json;
	}

	public static double round(double value, int places) {
		if (places < 0)
			throw new IllegalArgumentException();

		long factor = (long) Math.pow(10, places);
		value = value * factor;
		long tmp = Math.round(value);
		return (double) tmp / factor;
	}

	/**
	 * Display a simple alert dialog with the given text and title.
	 * 
	 * @param context
	 *            Android context in which the dialog should be displayed
	 * @param title
	 *            Alert dialog title
	 * @param text
	 *            Alert dialog message
	 */
	public static void showAlert(Context context, String title, String text) {
		Builder alertBuilder = new Builder(context);
		alertBuilder.setTitle(title);
		alertBuilder.setMessage(text);
		alertBuilder.create().show();
	}

	// given facebook Id and wanted picture size returns user profile picture
	public static Bitmap GetProfilePic(String facebookID, String picSize) {
		String pic_url = "https://graph.facebook.com/" + facebookID
				+ "/picture?type=" + picSize + "";
		return GetBitmapFromUrl(pic_url);
	}

	// loads a picture given a url
	public static Bitmap GetBitmapFromUrl(String url) {
		HttpClient client = new DefaultHttpClient();
		HttpGet get = new HttpGet(url);
		Bitmap bitmap = null;
		HttpResponse response = null;

		try {
			response = client.execute(get);
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			bitmap = BitmapFactory.decodeStream(response.getEntity()
					.getContent());
		} catch (IllegalStateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return bitmap;
	}

	// posts a string on users wall
	public static boolean PostStatusToFeed(Facebook mFacebook, String msg,
			Context c) {

		if (mFacebook != null) // check if facebook instance is active (may
								// be
		// non-active depending on facebook servers
		// availability)
		{

			if (!mFacebook.isSessionValid()) {
				// one last chance to restore session if invalid
				SessionStore.restore(mFacebook, c);
			}

			// check if facebook session is valid
			if (mFacebook.isSessionValid()) {

				try {
					String response = mFacebook.request("me");
					Bundle parameters = new Bundle();
					parameters.putString("message", msg);
					parameters.putString("description", "test test test");
					response = mFacebook.request("me/feed", parameters, "POST");
					if (response == null || response.equals("")
							|| response.equals("false")) {
						Log.e("error on posting status", "blank response");
						return false;
					}
				} catch (Exception e) {
					Log.e("error on posting status", e.toString());
					return false;
				}
			}

		} else {
			Log.e("error", "error on posting status");
		}

		return true;
	}

	// posting status in case no friends left to send to
	public static boolean PostLonelyStatus(Facebook mFacebook, Context c) {
		String msg = "is now using SporTeam and is looking for friends to share activities with!";
		return PostStatusToFeed(mFacebook, msg, c);
	}

	// post an image on facebook wall given from a URL
	// parameters fb - facebook object, imageURL - , caption - image title,
	// linkURL - redirect to this URL on click
	public static void PostImageFromURLToFeed(Facebook fb, String imageURL,
			String caption, String description, String name, String linkURL) {
		if (fb != null) {
			if (fb.isSessionValid()) {
				// setup bundle
				Bundle b = new Bundle();
				b.putString("picture", imageURL);
				b.putString("caption", caption);
				b.putString("description", description);
				b.putString("name", name);
				b.putString("link", linkURL);
				try {
					String strRet = "";
					strRet = fb.request("/me/feed", b, "POST");
					JSONObject json;
					try {
						json = parseJson(strRet);
						if (!json.isNull("id"))
							Log.i("Facebook", "Image link submitted.");
						else
							Log.e("Facebook", "Error: " + strRet);
					} catch (FacebookError e) {
						Log.e("Facebook", "Error: " + e.getMessage());
					}
				} catch (Exception e) {
					Log.e("Facebook", "Error: " + e.getMessage());
				}
			}
		}

	}

	// posts bitmap on facebook wall
	// parameters fb - facebook object
	// Bitmap - image uploaded to feed
	// caption - image title
	// description - some description describing the image (optional)
	// name - the link writing which redirects him to the linkURL
	// linkURL - redirect to this URL on click from facebook feed
	public static boolean PostImageFromBitmap(Facebook fb, Bitmap bitmap,
			String caption, String description, String name, String linkURL,
			Context c) {
		JSONObject json;
		String strRet = "";

		try {
			if (fb != null) // check if facebook instance is active (may be
							// non-active depending on facebook servers
							// availability)
			{

				if (!fb.isSessionValid()) {
					// one last chance to restore session if invalid
					SessionStore.restore(fb, c);
				}

				// check if facebook session is valid
				if (fb.isSessionValid()) {

					Bundle b = new Bundle();

					// compress bitmap to JPEG and convert to byte array
					if (bitmap != null) {
						ByteArrayOutputStream blob = new ByteArrayOutputStream();
						bitmap.compress(Bitmap.CompressFormat.JPEG, 100, blob);
						byte[] imageBytes = blob.toByteArray();
						b.putByteArray("picture", imageBytes);
					} else {
						byte[] imageBytes = new byte[1];
						b.putByteArray("picture", imageBytes);
					}
					// setup Bundle for facebook request
					b.putString("caption", caption);
					b.putString("description", description);
					b.putString("name", name);
					b.putString("link", linkURL);

					try {
						strRet = fb.request("/me/feed", b, "POST");

						try {
							json = parseJson(strRet);
							if (!json.isNull("id"))
								Log.i("Facebook", "Image link submitted."); // image
																			// upload
																			// successful
							else {
								Log.e("Facebook", "Error: " + strRet); // image
																		// upload
																		// failed

								return false;
							}
						} catch (FacebookError e) {
							Log.e("Facebook", "Error: " + e.toString());
							return false;
						}
					} catch (Exception e) {
						Log.e("Facebook", "Error: " + e.toString());
						return false;
					}
				} else
					return false;
			} else
				return false;
		} catch (Exception e) {
			Log.e("error on post", e.toString());
			return false;
		}

		return true;

	}

	static String name;
	static String caption;
	static String description;
	static String link;

	@SuppressWarnings("deprecation")
	private static void GetTrackAttributes(Track t) {

		Date trackDate = new Date(t.getDate());
		long tomeOffsetInM = trackDate.getTimezoneOffset();
		trackDate.setTime(trackDate.getTime() + 3 * 60 * 60 * 1000
				+ tomeOffsetInM * 60 * 1000);

		double distance = t.getDistance();
		double timeInM = t.getTime() / 1000 / 60;
		int TimeInH;
		String TrackTimeStr;
		String CaloriesStr;
		String SpeedStr;
		String TimeInMinutesStr;
		String DateRep;

		if (timeInM >= 60) {
			TimeInH = (int) (timeInM / 60);

			timeInM = Math.round(timeInM - TimeInH * 60);

			if (timeInM == 0) {
				TimeInMinutesStr = "";
			} else if (timeInM == 1) {
				TimeInMinutesStr = " and 1 minute";
			} else {
				TimeInMinutesStr = " and " + (int) timeInM + " minutes";
			}

			if (TimeInH == 1) {
				TrackTimeStr = "running time: 1 hour" + TimeInMinutesStr;
			} else {
				TrackTimeStr = "running time: " + TimeInH + " hours"
						+ TimeInMinutesStr;
			}

		} else {

			if (timeInM == 1) {
				TimeInMinutesStr = "1 minute";
			} else {
				TimeInMinutesStr = (int) timeInM + " minutes";
			}

			TrackTimeStr = "running time: " + TimeInMinutesStr;
		}

		if (t.GetCalories() == 0) {
			CaloriesStr = "";
		} else {
			CaloriesStr = " | calories burned: " + (int) t.GetCalories();
		}

		if (t.GetAvgSpeed() == 0) {
			SpeedStr = "";
		} else {
			SpeedStr = " | average speed: " + round(t.GetAvgSpeed(), 1);
		}

		// build name
		if (distance < 1000) {
			name = "completed a " + (int) distance + " meter run with SporTeam";
		} else {
			distance = distance / 1000;
			name = "completed a " + round(distance, 2)
					+ " km run with SporTeam";
		}

		// build caption
		DateRep = trackDate.toGMTString();
		caption = "on " + DateRep.substring(0, DateRep.length() - 3);

		// build description
		description = TrackTimeStr + CaloriesStr + SpeedStr;

		// TODO temporary link
		link = "http://sporteamws.appspot.com/";

		/*
		 * should be something like link =
		 * "http://sporteamws.appspot.com/ShowTrack?facebookID=" +
		 * t.GetSenderID() +"&trackID=" + t.getDate();
		 */
	}

	public static boolean PostTrackToFacebook(Facebook fb, Track t, Context c) {

		GetTrackAttributes(t);
		return PostImageFromBitmap(fb, null, caption, description, name, link,
				c);
	}

	public static boolean PostTrackWithImgToFacebook(Facebook fb,
			Bitmap bitmap, Track t, Context c) {

		GetTrackAttributes(t);

		return PostImageFromBitmap(fb, bitmap, caption, description, name,
				link, c);
	}

	public final class PhotoDialogListener implements RequestListener {

		@Override
		public void onComplete(String response, Object state) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onIOException(IOException e, Object state) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onFileNotFoundException(FileNotFoundException e,
				Object state) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onMalformedURLException(MalformedURLException e,
				Object state) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onFacebookError(FacebookError e, Object state) {
			// TODO Auto-generated method stub

		}

	}

}
